package com.util;

import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.common.Const;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * HmacSHA256工具类
 * @author Eason
 */
public class HmacSHA256Utils {

	private static Logger logger = LoggerFactory.getLogger(HmacSHA256Utils.class);
	
	/**
	 * 使用指定的密码对内容生成消息摘要（散列值）
	 * @param key
	 * @param content
	 * @return
	 * @author Eason
	 */
    public static String digest(String key, String content) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            byte[] secretByte = key.getBytes("utf-8");
            byte[] dataBytes = content.getBytes("utf-8");

            SecretKey secret = new SecretKeySpec(secretByte, "HMACSHA256");
            mac.init(secret);

            byte[] doFinal = mac.doFinal(dataBytes);
            byte[] hexB = new Hex().encode(doFinal);
            return new String(hexB, "utf-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用指定的密码对整个Map的内容生成消息摘要（散列值）
     * 逻辑：
     * 1、传入key(密钥)和需要加密的数组map.
     * 2、将数组按照key(map的键)排序,将值拼接成字符串. 
     * 3、生成摘要
     * @param key
     * @param map
     * @return
     */
    public static String digest(String key, Map<String, ?> map) {
    	
    	// 1、生成摘要前参数根据key排序后将value拼接成字符串。(前端以同样方式排序再生成摘要,以确保前后端生成摘要的方式相同.)
    	SortedMap<String, Object> sortedMap = new TreeMap<String,Object>(map);
    	StringBuilder content = new StringBuilder();
    	for(Object value : sortedMap.values()){
    		// 将obj转换成String(obj可能为String数组,以逗号分隔拼接成String)
    		content.append(objToString(value));
    	}
    	
    	// 2、生成摘要
        String digest = digest(key, content.toString());
        logger.info( "after sort: " + content.toString() +" key: " + key +" digest: " + digest );
        return digest;
    	
    	/* 另一种方法排序,并生成content
    	List<String> keys = new ArrayList<String>();
        for(String k: map.keySet()){
        	keys.add(k);
        }
        Collections.sort(keys);
        
        // 按照【根据key排序】后的顺序,生成参数字符串
        StringBuilder content = new StringBuilder();
        for(String k: keys){
        	String value = objToString( map.get(k) );
        	content.append(value);
        }*/
    }
    
    /**
     * 将Object参数转成String类型
     * 逻辑：
     * 1、若参数对象为String类型,直接返回
     * 2、若参数对象为String[]或List<String>类型,用","拼接成字符串并返回
     * 注：若数组对象中存在空字符串如数组{"a","","b"}会生成"a,,b",同前端javascript array.toString方法
     * @param obj
     * @return
     * @author Eason
     */
    public static String objToString(Object obj){
    	if (obj instanceof String){
    		return obj.toString();
    	}else if(obj instanceof String[] || obj instanceof List) {
    		return String.join(Const._COMMA,(String[])obj);
        } 
    	logger.info("params生成摘要时,objToString执行失败,obj:");
    	System.out.println(obj);
    	return "";
    }
}